class_name BuffSystem
extends BaseSystem

class BuffItem:
	var buff_agent:BuffComponent
	var buffs:Array[Buff] = []
	signal observer_action(buff)
	func _init(agent:BuffComponent) -> void:
		buff_agent = agent
		buffs = []

@export_range(0,10) var update_delta = 0.1
@export_range(0,1000) var gc_delta = 10
@export var buffs:Array[Buff] = []
var update_timer:Timer
var gc_timer:Timer
var buff_dict:Dictionary = {}
var buff_agent_dict:Dictionary = {}

func init_system():
	for buff:Buff in buffs:
		buff_dict[buff.buff_id] = buff
	update_timer = Timer.new()
	update_timer.name = "UpdateTimer"
	update_timer.process_callback = Timer.TIMER_PROCESS_PHYSICS
	update_timer.autostart = true
	update_timer.one_shot = false
	update_timer.wait_time = update_delta
	update_timer.timeout.connect(excute_update)
	add_child(update_timer)
	gc_timer = Timer.new()
	gc_timer.name = "GCTimer"
	gc_timer.process_callback = Timer.TIMER_PROCESS_PHYSICS
	gc_timer.autostart = true
	gc_timer.one_shot = false
	gc_timer.wait_time = gc_delta
	gc_timer.timeout.connect(excute_gc)
	add_child(gc_timer)
func start_observering(agent:BuffComponent,listener:Callable) -> Array[Buff]:
	if not buff_agent_dict.has(agent):
		buff_agent_dict[agent] = BuffItem.new(agent)
	var item:BuffItem = buff_agent_dict[agent]
	if not item.observer_action.is_connected(listener):
		item.observer_action.connect(listener)
	return item.buffs
func stop_observing(agent:BuffComponent,listener:Callable):
	if not buff_agent_dict[agent]:
		return
	var item:BuffItem = buff_agent_dict[agent]
	if item.observer_action.is_connected(listener):
		item.observer_action.disconnect(listener)
func get_buff_by_id(id:String) -> Buff:
	if buff_dict.has(id):
		var buff:Buff = buff_dict[id]
		return buff.duplicate(true)
	return null
func add_buff_by_id(provier,agent:BuffComponent,id:String,level:int = 1):
	if not buff_agent_dict.has(agent):
		buff_agent_dict[agent] = BuffItem.new(agent)
		add_new_buff_by_id(provier,agent,id,level)
		return
	var item:BuffItem = buff_agent_dict[agent]
	if item.buffs.size() == 0:
		add_new_buff_by_id(provier,agent,id,level)
		return
	var cur_buffs:Array[Buff] = find_buff_by_id(agent,id)
	if cur_buffs.size() == 0:
		add_new_buff_by_id(provier,agent,id,level)
	else:
		match cur_buffs[0].conflict_resolution:
			GameEnum.BuffConflictResolution.Separate:
				var is_existed:bool = false
				for buff:Buff in cur_buffs:
					if buff.provider == provier:
						buff.cur_level += level
						is_existed = true
						continue
				if not is_existed:
					add_new_buff_by_id(provier,agent,id,level)
			GameEnum.BuffConflictResolution.Combine:
				cur_buffs[0].cur_level += level
			GameEnum.BuffConflictResolution.Cover:
				remove_buff_by_id(agent,cur_buffs[0].buff_id)
				add_new_buff_by_id(provier,agent,id,level)
	for buff in item.buffs:
		buff.on_update_buff_list.emit()
func add_new_buff_by_id(provier,agent:BuffComponent,id:String,level:int):
	var buff:Buff = buff_dict[id].duplicate(true)
	buff.init_buff(provier,agent)
	var item:BuffItem = buff_agent_dict[agent]
	item.buffs.append(buff)
	buff.residual_duration = buff.max_duration
	buff.cur_level = level
	buff.on_get_buff.emit()
	item.observer_action.emit(buff)
func add_buff(provier,agent:BuffComponent,temp_buff:Buff,level:int = 1):
	if not buff_agent_dict.has(agent):
		buff_agent_dict[agent] = BuffItem.new(agent)
		add_new_buff(provier,agent,temp_buff,level)
		return
	var item:BuffItem = buff_agent_dict[agent]
	if item.buffs.size() == 0:
		add_new_buff(provier,agent,temp_buff,level)
		return
	var cur_buffs:Array[Buff] = find_buff_by_id(agent,temp_buff.buff_id)
	if cur_buffs.size() == 0:
		add_new_buff(provier,agent,temp_buff,level)
	else:
		match cur_buffs[0].conflict_resolution:
			GameEnum.BuffConflictResolution.Separate:
				var is_existed:bool = false
				for buff:Buff in cur_buffs:
					if buff.provider == provier:
						buff.cur_level += level
						is_existed = true
						continue
				if not is_existed:
					add_new_buff(provier,agent,temp_buff,level)
			GameEnum.BuffConflictResolution.Combine:
				cur_buffs[0].cur_level += level
			GameEnum.BuffConflictResolution.Cover:
				remove_buff_by_id(agent,cur_buffs[0].buff_id)
				add_new_buff(provier,agent,temp_buff,level)
	for buff in item.buffs:
		buff.on_update_buff_list.emit()
func add_new_buff(provier,agent:BuffComponent,temp_buff:Buff,level:int):
	temp_buff.init_buff(provier,agent)
	var item:BuffItem = buff_agent_dict[agent]
	item.buffs.append(temp_buff)
	temp_buff.residual_duration = temp_buff.max_duration
	temp_buff.cur_level = level
	temp_buff.on_get_buff.emit()
	item.observer_action.emit(temp_buff)
func find_buff_by_id(agent:BuffComponent,id:String) -> Array[Buff]:
	var res:Array[Buff] = []
	if buff_agent_dict.has(agent):
		res = buff_agent_dict[agent].buffs.filter(func(buff:Buff):return buff.buff_id == id)
	return res
func find_all_buff(agent:BuffComponent) -> Array[Buff]:
	var res:Array[Buff] = []
	if buff_agent_dict.has(agent):
		res = buff_agent_dict[agent].buffs
	return res
func remove_buff_by_id(agent:BuffComponent,id:String) -> bool:
	var cur_buffs:Array[Buff] = find_buff_by_id(agent,id)
	if cur_buffs.size() == 0:
		return false
	for buff in cur_buffs:
		remove_buff(agent,buff)
	return true
func remove_buff(agent:BuffComponent,buff:Buff) -> bool:
	if buff == null:
		return false
	buff.cur_level -= buff.cur_level
	buff.on_lost_buff.emit()
	var item:BuffItem = buff_agent_dict[agent]
	item.buffs.erase(buff)
	for b in item.buffs:
		b.on_update_buff_list.emit()
	return true
func remove_buff_by_type(agent:BuffComponent,buff_type:GameEnum.BuffType) -> bool:
	if not buff_agent_dict.has(agent):
		return false
	for buff:Buff in buff_agent_dict[agent].buffs.filter(func(b:Buff):return b.buff_type == buff_type):
		if buff.dispellable == false:
			continue
		buff.cur_level -= buff.cur_level
		buff.on_lost_buff.emit()
		buff_agent_dict[agent].buffs.erase(buff)
	for buff:Buff in buff_agent_dict[agent].buffs:
		buff.on_update_buff_list.emit()
	return true
func remove_buff_by_tag(agent:BuffComponent,buff_tag:GameEnum.BuffTag) -> bool:
	if not buff_agent_dict.has(agent):
		return false
	for buff:Buff in buff_agent_dict[agent].buffs.filter(func(b:Buff):return b.buff_tag&buff_tag > 0):
		if buff.dispellable == false:
			continue
		buff.cur_level -= buff.cur_level
		buff.on_lost_buff.emit()
		buff_agent_dict[agent].buffs.erase(buff)
	for buff:Buff in buff_agent_dict[agent].buffs:
		buff.on_update_buff_list.emit()
	return true
func remove_all_buff(agent:BuffComponent):
	if not buff_agent_dict.has(agent):
		return false
	for buff:Buff in buff_agent_dict[agent].buffs:
		buff.cur_level -= buff.cur_level
		buff.on_lost_buff.emit()
	buff_agent_dict[agent].buffs.clear()
	buff_agent_dict.erase(agent)
	return true
func excute_update():
	for item:BuffItem in buff_agent_dict.values():
		for buff:Buff in item.buffs:
			if buff.cur_level > 0 and buff.cur_buff_agent:
				buff.on_update_buff.emit()
				buff.residual_duration -= update_delta
	for agent in buff_agent_dict:
		var item:BuffItem = buff_agent_dict[agent]
		for i in range(item.buffs.size()-1,-1,-1):
			var buff:Buff = item.buffs[i]
			if buff.cur_level == 0:
				remove_buff(agent,buff)
				continue
			if buff.residual_duration <= 0:
				buff.cur_level -= buff.demotion
				if buff.cur_level == 0:
					remove_buff(agent,buff)
					continue
				else:
					buff.residual_duration = buff.max_duration
func excute_gc():
	var agents:Array[BuffComponent] = []
	for agent in buff_agent_dict:
		if agent == null:
			agents.append(agent)
		var item:BuffItem = buff_agent_dict[agent]
		if item.buffs.size() == 0 and item.observer_action.get_connections().size() == 0:
			agents.append(agent)
	for agent in agents:
		buff_agent_dict.erase(agent)
